//===--- HeapTypeInfo.h - Utilities for reference-counted types -*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// This file defines some routines that are useful for emitting
// types that are single, reference-counted pointers.
//
//===----------------------------------------------------------------------===//

#ifndef POLARPHP_IRGEN_INTERNAL_HEAPTYPEINFO_H
#define POLARPHP_IRGEN_INTERNAL_HEAPTYPEINFO_H

#include "llvm/IR/DerivedTypes.h"
#include "polarphp/irgen/internal/ExtraInhabitants.h"
#include "polarphp/irgen/internal/ReferenceTypeInfo.h"
#include "polarphp/irgen/internal/ScalarTypeInfo.h"
#include "polarphp/irgen/internal/PolarphpTargetInfo.h"
#include "polarphp/irgen/internal/GenType.h"

namespace polar::irgen {

/// \group HeapTypeInfo

/// The kind of 'isa' encoding a heap object uses to reference its heap
/// metadata.
enum class IsaEncoding : uint8_t {
   /// The object stores a plain pointer to its heap metadata as its first word.
      Pointer,
   /// The object's isa is managed by the Objective-C runtime and must be
   /// accessed with object_getClass. This is a superset of "Pointer" because
   /// object_getClass is compatible with pointer isas.
      ObjC,
   /// The isa encoding is unknown and must be accessed in a maximally-compatible
   /// way.
      Unknown = ObjC,
};

/// HeapTypeInfo - A type designed for use implementing a type
/// which consists solely of something reference-counted.
///
/// Subclasses should implement the following method, returning true
/// if it's known to be OK to use Swift reference-counting on values
/// of this type:
///   ReferenceCounting getReferenceCounting() const;
template <class Impl>
class HeapTypeInfo : public SingleScalarTypeInfo<Impl, ReferenceTypeInfo> {
   using super = SingleScalarTypeInfo<Impl, ReferenceTypeInfo>;

   llvm::Type *getOptionalIntType() const {
      return llvm::IntegerType::get(this->getStorageType()->getContext(),
                                    this->getFixedSize().getValueInBits());
   }

protected:
   using super::asDerived;
public:
   HeapTypeInfo(llvm::PointerType *storage, Size size, SpareBitVector spareBits,
                Alignment align)
      : super(storage, size, spareBits, align) {}

   bool isSingleRetainablePointer(ResilienceExpansion expansion,
                                  ReferenceCounting *refcounting) const override {
      if (refcounting)
         *refcounting = asDerived().getReferenceCounting();
      return true;
   }

   IsaEncoding getIsaEncoding(ResilienceExpansion expansion) const {
      switch (asDerived().getReferenceCounting()) {
         // We can access the isa of pure Swift heap objects directly.
         case ReferenceCounting::Native:
            return IsaEncoding::Pointer;
            // Use the ObjC runtime to access ObjC or mixed-heritage isas.
            // @todo
//         case ReferenceCounting::ObjC:
         case ReferenceCounting::Block:
            return IsaEncoding::ObjC;
         case ReferenceCounting::Unknown:
            return IsaEncoding::Unknown;
         case ReferenceCounting::Error:
            llvm_unreachable("errortype doesn't have an isa");
      }
   }

   static const bool IsScalarPOD = false;

   // Emit the copy/destroy operations required by SingleScalarTypeInfo
   // using strong reference counting.
   void emitScalarRelease(IRGenFunction &IGF, llvm::Value *value,
                          Atomicity atomicity) const {
      IGF.emitStrongRelease(value, asDerived().getReferenceCounting(), atomicity);
   }

   void emitScalarFixLifetime(IRGenFunction &IGF, llvm::Value *value) const {
      return IGF.emitFixLifetime(value);
   }

   void emitScalarRetain(IRGenFunction &IGF, llvm::Value *value,
                         Atomicity atomicity) const {
      IGF.emitStrongRetain(value, asDerived().getReferenceCounting(), atomicity);
   }

   // Implement the primary retain/release operations of ReferenceTypeInfo
   // using basic reference counting.
   void strongRetain(IRGenFunction &IGF, Explosion &e,
                     Atomicity atomicity) const override {
      llvm::Value *value = e.claimNext();
      asDerived().emitScalarRetain(IGF, value, atomicity);
   }

   void strongRelease(IRGenFunction &IGF, Explosion &e,
                      Atomicity atomicity) const override {
      llvm::Value *value = e.claimNext();
      asDerived().emitScalarRelease(IGF, value, atomicity);
   }

#define REF_STORAGE_HELPER(Name) \
  const TypeInfo * \
  create##Name##StorageType(TypeConverter &TC, \
                            bool isOptional) const override { \
    return TC.create##Name##StorageType(this->getStorageType(), \
                                        asDerived().getReferenceCounting(), \
                                        isOptional); \
  }
#define NEVER_LOADABLE_CHECKED_REF_STORAGE_HELPER(Name, name) \
  void name##LoadStrong(IRGenFunction &IGF, Address src, \
                         Explosion &out, bool isOptional) const override { \
    llvm::Value *value = IGF.emit##Name##LoadStrong(src, \
                                          this->getStorageType(), \
                                          asDerived().getReferenceCounting()); \
    if (isOptional) { \
      out.add(IGF.Builder.CreatePtrToInt(value, getOptionalIntType())); \
    } else { \
      out.add(value); \
    } \
  } \
  void name##TakeStrong(IRGenFunction &IGF, Address src, \
                         Explosion &out, bool isOptional) const override { \
    llvm::Value *value = IGF.emit##Name##TakeStrong(src, \
                                          this->getStorageType(), \
                                          asDerived().getReferenceCounting()); \
    if (isOptional) { \
      out.add(IGF.Builder.CreatePtrToInt(value, getOptionalIntType())); \
    } else { \
      out.add(value); \
    } \
  } \
  void name##Init(IRGenFunction &IGF, Explosion &in, \
                  Address dest, bool isOptional) const override { \
    llvm::Value *value = in.claimNext(); \
    if (isOptional) { \
      assert(value->getType() == getOptionalIntType()); \
      value = IGF.Builder.CreateIntToPtr(value, this->getStorageType()); \
    } \
    IGF.emit##Name##Init(value, dest, asDerived().getReferenceCounting()); \
  } \
  void name##Assign(IRGenFunction &IGF, Explosion &in, \
                    Address dest, bool isOptional) const override { \
    llvm::Value *value = in.claimNext(); \
    if (isOptional) { \
      assert(value->getType() == getOptionalIntType()); \
      value = IGF.Builder.CreateIntToPtr(value, this->getStorageType()); \
    } \
    IGF.emit##Name##Assign(value, dest, asDerived().getReferenceCounting()); \
  }
#define ALWAYS_LOADABLE_CHECKED_REF_STORAGE_HELPER(Name, name) \
  void strongRetain##Name(IRGenFunction &IGF, Explosion &e, \
                          Atomicity atomicity) const override { \
    llvm::Value *value = e.claimNext(); \
    assert(asDerived().getReferenceCounting() == ReferenceCounting::Native); \
    IGF.emitNativeStrongRetain##Name(value, atomicity); \
  } \
  void strongRetain##Name##Release(IRGenFunction &IGF, Explosion &e, \
                                   Atomicity atomicity) const override { \
    llvm::Value *value = e.claimNext(); \
    assert(asDerived().getReferenceCounting() == ReferenceCounting::Native); \
    IGF.emitNativeStrongRetainAnd##Name##Release(value, atomicity); \
  } \
  void name##Retain(IRGenFunction &IGF, Explosion &e, \
                    Atomicity atomicity) const override { \
    llvm::Value *value = e.claimNext(); \
    assert(asDerived().getReferenceCounting() == ReferenceCounting::Native); \
    IGF.emitNative##Name##Retain(value, atomicity); \
  } \
  void name##Release(IRGenFunction &IGF, Explosion &e, \
                      Atomicity atomicity) const override { \
    llvm::Value *value = e.claimNext(); \
    assert(asDerived().getReferenceCounting() == ReferenceCounting::Native); \
    IGF.emitNative##Name##Release(value, atomicity); \
  }
#define NEVER_LOADABLE_CHECKED_REF_STORAGE(Name, name, ...) \
  NEVER_LOADABLE_CHECKED_REF_STORAGE_HELPER(Name, name) \
  REF_STORAGE_HELPER(Name)
#define ALWAYS_LOADABLE_CHECKED_REF_STORAGE(Name, name, ...) \
  ALWAYS_LOADABLE_CHECKED_REF_STORAGE_HELPER(Name, name) \
  REF_STORAGE_HELPER(Name)
#define SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name, name, ...) \
  NEVER_LOADABLE_CHECKED_REF_STORAGE_HELPER(Name, name) \
  ALWAYS_LOADABLE_CHECKED_REF_STORAGE_HELPER(Name, name) \
  REF_STORAGE_HELPER(Name)
#define UNCHECKED_REF_STORAGE(Name, ...) \
  REF_STORAGE_HELPER(Name)
#include "polarphp/ast/ReferenceStorageDef.h"
#undef REF_STORAGE_HELPER
#undef NEVER_LOADABLE_CHECKED_REF_STORAGE_HELPER
#undef ALWAYS_LOADABLE_CHECKED_REF_STORAGE_HELPER

   LoadedRef loadRefcountedPtr(IRGenFunction &IGF, SourceLoc loc,
                               Address addr) const override {
      llvm::Value *ptr =
         IGF.emitLoadRefcountedPtr(addr, asDerived().getReferenceCounting());
      return LoadedRef(ptr, true);
   }

   // Extra inhabitants of heap object pointers.

   bool mayHaveExtraInhabitants(IRGenModule &IGM) const override {
      return true;
   }

   unsigned getFixedExtraInhabitantCount(IRGenModule &IGM) const override {
      return getHeapObjectExtraInhabitantCount(IGM);
   }

   APInt getFixedExtraInhabitantValue(IRGenModule &IGM,
                                      unsigned bits,
                                      unsigned index) const override {
      return getHeapObjectFixedExtraInhabitantValue(IGM, bits, index, 0);
   }

   llvm::Value *getExtraInhabitantIndex(IRGenFunction &IGF, Address src,
                                        PILType T, bool isOutlined)
   const override {
      return getHeapObjectExtraInhabitantIndex(IGF, src);
   }

   void storeExtraInhabitant(IRGenFunction &IGF, llvm::Value *index,
                             Address dest, PILType T, bool isOutlined)
   const override {
      return storeHeapObjectExtraInhabitant(IGF, index, dest);
   }
};

} // polar::irgen

#endif // POLARPHP_IRGEN_INTERNAL_HEAPTYPEINFO_H
